"""插值运算是一种数据处理方法，主要用来填补数据之间的空白或缺失值。
因为在实际应用中，数据往往不是完整的，而是存在着空白或缺失值，
这些空白或缺失值可能是由于数据采集困难、数据丢失或数据处理错误等原因造成的。
如果直接使用这些空白或缺失值进行分析和预测，将会对结果造成很大的影响。

插值运算可以用来填补这些空白或缺失值，从而恢复完整的数据集。
通过插值运算，可以估算出空白或缺失值的值，从而提高数据的完整性和准确性。
此外，插值运算还可以用来预测未来的数据趋势或结果，对于数据分析和预测具有重要的意义。

本篇介绍Scipy为我们提供的插值处理方法。

1. 主要功能
Scipy中，关于插值的子模块是：scipy.interpolate。
其中又细分为：

类别	说明
单变量插值	主要包含interp1d等12个函数
多变量插值	主要包含griddata等11个函数
一维样条函数	主要包含BSpline等16个函数
二维样条函数	主要包含RectBivariateSpline等9个函数
其他函数	一些辅助计算的函数
插值效果的好坏，有个重要的因素在于是否根据数据的情况选择了合适的插值算法。
Scipy库中已经实现的插值算法有：

linear：线性插值算法
nearest：最近邻插值算法
nearest-up：改进型最近邻插值算法
zero：零阶样条插值算法（等同于 previous）
slinear：一阶样条插值算法（等同于 linear）
quadratic：五阶样条插值算法
cubic：三阶样条插值算法
previous：前点插值算法
next：后点插值算法
我们可以根据数据情况选择合适的算法，下面用一些测试数据演示不同算法的插值效果："""
import numpy as np
from matplotlib import pyplot as plt

import matplot_config

matplot_config.init_config()
from scipy.interpolate import interp1d

x = np.linspace(0, 20, 20)
y = x * np.cos(x)
plt.scatter(x, y)
plt.show()
"""image.png
上图是插值之前，直接由20个数据点连接起来的折线。

接下来，应用9种不同的插值算法将20个点补充为100个点，然后看看插值之后各种曲线的效果。
"""
interp_types = [
    "linear",
    "nearest",
    "nearest-up",
    "zero",
    "slinear",
    "quadratic",
    "cubic",
    "previous",
    "next",
]

fig = plt.figure(figsize=[12, 9])
fig.subplots_adjust(hspace=0.4)

for idx, typ in enumerate(interp_types):
    f = interp1d(x, y, kind=typ)
    x_dense = np.linspace(0, 20, 100)
    y_dense = f(x_dense)

    ax = fig.add_subplot(330 + idx + 1)
    ax.scatter(x, y)
    ax.plot(x_dense, y_dense, color='g')
    ax.set_title("插值算法-{}".format(typ))

plt.show()
"""image.png

2. 一维插值示例
在我自己实际接触的项目中，气象数据的处理经常会用到插值。
因为气象数据常常存在缺失值，这可能是由于传感器故障，数据传输问题，或者在某些情况下，由于天气现象使得数据无法收集。
对于这些缺失值，我们可以使用Scipy的一维插值功能来进行填充。

比如下面是南京市某年的各个月的平均气温：
"""
import pandas as pd

# 南京一年中每个月平均气温
df = pd.DataFrame({
    "月份": ["一月", "二月", "三月",
             "四月", "五月", "六月",
             "七月", "八月", "九月",
             "十月", "十一月", "十二月"],
    "平均最低气温": [-1.6, 0.0, 4.4,
                     np.nan, 15.7, 20.4,
                     np.nan, 24.6, 19.1,
                     12.6, 6.1, -0.1],
    "平均最高气温": [7.0, 8.4, np.nan,
                     20.1, 25.3, 29.0,
                     32.0, 32.2, 27.2,
                     np.nan, 16.9, 9.7],
})

df
"""image.png
由于采集或者传输的原因，导致缺失了一些数据。
这样的数据不仅绘制出来的折线图会有断开的地方，而且不利于后续的分析。"""

plt.plot(df["月份"], df["平均最低气温"], label="平均最低气温")
plt.plot(df["月份"], df["平均最高气温"], label="平均最高气温")
plt.legend()

plt.show()
"""image.png

这时，可以用Scipy的插值算法来补充缺失数据。"""

from scipy.interpolate import interp1d

# 过滤掉缺失的 平均最低气温 数据
df_low = df[df["平均最低气温"].notna()]
df_low.round(1)
# 根据已有的数据生成插值函数
f = interp1d(df_low.index, df_low["平均最低气温"], kind="cubic")
# 用插值函数补充缺失数据
print(f(range(12)))
df["平均最低气温"] = f(range(12))

# 平均最高气温 的缺失数据处理同上
df_high = df[df["平均最高气温"].notna()]
f = interp1d(df_high.index, df_high["平均最高气温"], kind="cubic")
df["平均最高气温"] = f(range(12))

df.round(1)

plt.plot(df["月份"], df["平均最低气温"], label="平均最低气温")
plt.plot(df["月份"], df["平均最高气温"], label="平均最高气温")
plt.legend()

plt.show()
"""image.png
处理之后，数据的连续性更好了。

3. 二维插值示例
当自变量有2个的时候，就要用到二维插值了。
仍然以气象上的数据举例，上面示例是气温和时间的关系，我们把时间作为自变量，只要一维插值即可。
如果是和地点关联的话，那么地点作为自变量就有2个值（一般是经度和纬度）。

比如下面截取了项目中一段降水量的数据："""

# 数据是二维数组：
# 每一行代表经度相同，纬度不同的地点
# 每一列代表纬度相同，经度不同的地点
data = np.array([
    [9, 9, 5, 9, 10, 9, 8, 7, 11, 1],
    [54, 36, 54, 32, 46, 51, 35, 33, 36, 11],
    [35, 34, 34, 45, 52, 35, 34, 36, 41, 9],
    [117, 112, 113, 133, 126, 127, 119, 96, 116, 23],
    [110, 67, 91, 85, 94, 69, 77, 81, 65, 13],
    [9, 7, 13, 12, 9, 6, 8, 9, 21, 3],
    [50, 21, 24, 32, 36, 26, 28, 30, 24, 3],
    [65, 41, 63, 67, 58, 50, 54, 45, 48, 16],
    [36, 29, 32, 28, 38, 29, 41, 27, 29, 9],
    [37, 61, 57, 35, 56, 51, 40, 58, 100, 34],
])
# 显示降水量的分布情况。

plt.imshow(data, cmap=plt.cm.GnBu)
plt.colorbar()
plt.xticks([])
plt.yticks([])
plt.show()
"""image.png
data中只有100个数据，所以每个格子一个值，看起来不是那么连续，
而实际的降水情况不会像这样离散的，区域之间的降水量应该是逐渐连续变化的。

所以，需要在data的基础上进行二维插值：
"""
from scipy.interpolate import RectBivariateSpline

# 原始数据是 10x10
x = np.linspace(0, 10, 10, endpoint=False)
y = np.linspace(0, 10, 10, endpoint=False)

# 插值后的数据是 500x500
x_new = np.linspace(0, 10, 500, endpoint=False)
y_new = np.linspace(0, 10, 500, endpoint=False)

# 从原始数据生成插值函数
f = RectBivariateSpline(x, y, data.T)
# 用插值函数计算新的数据
data_new = f(x_new, y_new)

# 显示插值后的结果
plt.imshow(data_new.T, cmap=plt.cm.GnBu)
plt.colorbar()
plt.xticks([])
plt.yticks([])
plt.show()
"""image.png
这是插值到 500x500 的效果，值越大，连续性越好。
有兴趣的话，可以调整上面代码中 x_new 和 y_new 的个数，看看不同的效果。

4. 总结
插值作为一种常见的数据处理方法，应用的领域和场景非常多，比如：

数据预测：通过插值技术，可以预测未来的数据趋势或结果。
图像处理：插值可以用于图像处理和图像分析，以提高图像的分辨率或质量。
机器学习：插值技术也可以用于机器学习和人工智能领域。用于构建回归模型或分类模型，以便对未知数据进行预测或分类。
本文主要介绍了Scipy库的插值子模块，其内置的插值算法，以及两个应用插值的小例子。"""
